package config

import (
	"context"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/client_core/service"
	"github.com/chicken-team-outside/chicken_transmission/utils"
	"go.uber.org/zap"
	"math"
	"net"
	"net/http"
	"sync/atomic"
	"time"
)

var httpClientConfig atomic.Value

type HttpClientSetting struct {
	DnsTimeout            int    `json:"dns_timeout"`
	Dns1                  string `json:"dns1"`
	Dns2                  string `json:"dns2"`
	DialerTimeout         int    `json:"dialer_timeout"`
	DialerKeepAlive       int    `json:"dialer_keep_alive"`
	MaxIdleConns          int    `json:"max_idle_conns"`
	IdleConnTimeout       int    `json:"idle_conn_timeout"`
	TlsHandshakeTimeout   int    `json:"tls_handshake_timeout"`
	ExpectContinueTimeout int    `json:"expect_continue_timeout"`
	MaxIdleConnsPerHost   int    `json:"max_idle_conns_per_host"`
}

func initHttpSetting() error {
	cfg := &HttpClientSetting{
		DnsTimeout:            2000,
		Dns1:                  "119.29.29.29:53",
		Dns2:                  "180.76.76.76:53",
		DialerTimeout:         30000,
		DialerKeepAlive:       300000,
		MaxIdleConns:          100,
		IdleConnTimeout:       90000,
		TlsHandshakeTimeout:   10000,
		ExpectContinueTimeout: 1000,
		MaxIdleConnsPerHost:   100,
	}
	err := service.GetOrSaveConfig([]byte("http_client_setting"), cfg)
	if err != nil {
		return fmt.Errorf("failed to get http client setting: %w", err)
	}
	httpClientConfig.Store(cfg)
	resetHttpClient(cfg)
	return nil
}

func SaveHttpConfig(cfg *HttpClientSetting) error {
	err := service.SaveConfigData([]byte("http_client_setting"), cfg)
	if err != nil {
		return err
	}
	httpClientConfig.Store(cfg)
	resetHttpClient(cfg)
	return nil
}

func HttpClientConfig() *HttpClientSetting {
	return httpClientConfig.Load().(*HttpClientSetting)
}

func resetHttpClient(cfg *HttpClientSetting) {
	dialer := &net.Dialer{Timeout: time.Duration(cfg.DnsTimeout) * time.Millisecond}
	var dns1 string
	if conn, err := dialer.Dial("udp", cfg.Dns1); err == nil {
		dns1 = cfg.Dns1
		conn.Close()
	} else {
		zap.L().Fatal("check dns server failed", zap.String("nameserver", cfg.Dns1), zap.Error(err))
	}
	var dns2 string
	if conn, err := dialer.Dial("udp", cfg.Dns2); err == nil {
		dns2 = cfg.Dns2
		conn.Close()
	} else {
		zap.L().Fatal("check dns server failed", zap.String("nameserver", cfg.Dns2), zap.Error(err))
	}
	var resolver *net.Resolver
	if dns1 == "" || dns2 == "" {
		resolver = net.DefaultResolver
	} else {
		resolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
				if dns1 != "" {
					conn, err = dialer.DialContext(ctx, "udp", dns1)
				}
				if conn == nil || err != nil {
					if dns2 != "" {
						conn, err = dialer.DialContext(ctx, "udp", dns2)
					}
				}
				return
			},
		}
	}
	maxIdleConns := cfg.MaxIdleConns
	if maxIdleConns <= 0 {
		maxIdleConns = math.MaxInt
	}
	maxIdleConnsPerHost := cfg.MaxIdleConnsPerHost
	if maxIdleConnsPerHost <= 0 {
		maxIdleConnsPerHost = math.MaxInt
	}
	client := &http.Client{
		Transport: &http.Transport{
			DialContext: (&net.Dialer{
				Timeout:   time.Duration(cfg.DialerTimeout) * time.Millisecond,
				KeepAlive: time.Duration(cfg.DialerKeepAlive) * time.Millisecond,
				Resolver:  resolver,
			}).DialContext,
			ForceAttemptHTTP2:     true,
			MaxIdleConns:          maxIdleConns,
			IdleConnTimeout:       time.Duration(cfg.IdleConnTimeout) * time.Millisecond,
			TLSHandshakeTimeout:   time.Duration(cfg.TlsHandshakeTimeout) * time.Millisecond,
			ExpectContinueTimeout: time.Duration(cfg.ExpectContinueTimeout) * time.Millisecond,
			MaxIdleConnsPerHost:   maxIdleConnsPerHost,
		},
	}
	utils.SetHttpClient(client)
}
